#include "TDDLHint.h"
#include "../parser/SQLParserFeature.h"
#include "../dialect/mysql/parser/MySqlExprParser.h"
#include "../../utils/FnvHash.h"
#include "expr/SQLPropertyExpr.h"

TDDLHint::TDDLHint(string_ptr text) : SQLCommentHint(text)
{
  functions = std::make_shared<std::list<TDDLHint_Function_ptr>>();
  SQLParserFeature_list_ptr features = std::make_shared<std::list<SQLParserFeature_ptr>>();
  features->push_back(SQLParserFeature_ptr(new SQLParserFeature(&SQLParserFeature::TDDLHint)));
  MySqlExprParser *hintParser = new MySqlExprParser(text, features);
  Lexer_ptr lexer = hintParser->getLexer();
  if (*lexer->token() == Token::PLUS || *lexer->token() == Token::BANG)
  {
    lexer->nextToken();
  }

  if (!lexer->identifierEquals(FnvHash::Constants::TDDL))
  {
    // error tddl hint
    return;
  }

  lexer->nextToken();

  if (*lexer->token() == Token::COLON)
  {
    lexer->nextToken();
  }
  else if (*lexer->token() == Token::LPAREN)
  {
    int rp = text->rfind(')');
    if (rp != -1)
    {
      json = make_string_ptr(text->substr(lexer->pos(), rp - lexer->pos()));
      type = TDDLHint_Type::JSON;
    }
    return;
  }
  else
  {
    return;
  }

  for (;;)
  {
    if (*lexer->token() == Token::AND)
    {
      lexer->nextToken();
    }

    string_ptr name = make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()));
    long hash = lexer->hashLCase();

    if (lexer->identifierEquals(FnvHash::Constants::NODE))
    {
      lexer->nextToken();
      if (*lexer->token() == Token::IN)
      {
        lexer->nextToken();
        name = make_string_ptr("NODE_IN");
      }
      else if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
        name = make_string_ptr("NODE_IN");
        SQLExpr_ptr value = hintParser->primary();
        TDDLHint_Function_ptr function = TDDLHint_Function_ptr(new TDDLHint_Function(name));
        TDDLHint_Argument_ptr argument = TDDLHint_Argument_ptr(new TDDLHint_Argument(NULL, value));
        function->getArguments()->push_back(argument);
        functions->push_back(function);

        if (*lexer->token() == Token::XEOF)
        {
          break;
        }

        continue;
      }
    }
    else if (hash == FnvHash::Constants::SCAN || hash == FnvHash::Constants::DEFER)
    {
      lexer->nextToken();

      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
        SQLExpr_ptr value = hintParser->primary();
        TDDLHint_Function_ptr function = TDDLHint_Function_ptr(new TDDLHint_Function(name));
        TDDLHint_Argument_ptr argument = TDDLHint_Argument_ptr(new TDDLHint_Argument(NULL, value));
        function->getArguments()->push_back(argument);
        functions->push_back(function);

        if (*lexer->token() == Token::XEOF)
        {
          break;
        }

        continue;
      }
      else if (*lexer->token() == Token::XEOF)
      {
        TDDLHint_Function_ptr function = TDDLHint_Function_ptr(new TDDLHint_Function(name));
        functions->push_back(function);
        break;
      }
    }
    else if (hash == FnvHash::Constants::SQL_DELAY_CUTOFF ||
             hash == FnvHash::Constants::SOCKET_TIMEOUT ||
             hash == FnvHash::Constants::UNDO_LOG_LIMIT ||
             hash == FnvHash::Constants::FORBID_EXECUTE_DML_ALL)
    {
      lexer->nextToken();

      if (*lexer->token() == Token::EQ)
      {
        lexer->nextToken();
        SQLExpr_ptr value = hintParser->primary();
        TDDLHint_Function_ptr function = TDDLHint_Function_ptr(new TDDLHint_Function(name));
        TDDLHint_Argument_ptr argument = TDDLHint_Argument_ptr(new TDDLHint_Argument(NULL, value));
        function->getArguments()->push_back(argument);
        functions->push_back(function);

        if (*lexer->token() == Token::XEOF)
        {
          break;
        }

        continue;
      }
    }
    else
    {
      lexer->nextToken();
    }

    // /!TDDL:table_name.partition_key=value [and table_name1.partition_key=value1]*/
    // =>
    // /!TDDL:partitions('table_name.partition_key=value','table_name1.partition_key=value1')*/
    if (*lexer->token() == Token::DOT)
    {
      lexer->nextToken();
      if (lexer->identifierEquals(make_string_ptr("partition_key")))
      {
        string_ptr table = name;
        lexer->nextToken();
        hintParser->accept(Token::EQ);
        SQLExpr_ptr value = hintParser->primary();

        TDDLHint_Function_ptr function = TDDLHint_Function_ptr(new TDDLHint_Function(make_string_ptr("PARTITIONS")));
        functions->push_back(function);

        SQLPropertyExpr_ptr sqlPropertyExpr = SQLPropertyExpr_ptr(new SQLPropertyExpr(name, make_string_ptr("partition_key")));
        function->getArguments()->push_back(TDDLHint_Argument_ptr(new TDDLHint_Argument(sqlPropertyExpr, value)));

        while (*lexer->token() == Token::AND)
        {
          lexer->nextToken();
          SQLExpr_ptr key = hintParser->primary();
          hintParser->accept(Token::EQ);
          value = hintParser->primary();

          function->getArguments()->push_back(TDDLHint_Argument_ptr(new TDDLHint_Argument(key, value)));
        }

        if (*lexer->token() == Token::XEOF)
        {
          break;
        }
      }
      else
      {
        return; // skip
      }
    }
    else if (*lexer->token() == Token::EQ)
    {
      lexer->nextToken();
      // For other KV.
      SQLExpr_ptr value = hintParser->primary();
      TDDLHint_Function_ptr function = TDDLHint_Function_ptr(new TDDLHint_Function(name));
      TDDLHint_Argument_ptr argument = TDDLHint_Argument_ptr(new TDDLHint_Argument(NULL, value));
      function->getArguments()->push_back(argument);
      functions->push_back(function);

      if (*lexer->token() == Token::XEOF)
      {
        break;
      }

      continue;
    }

    TDDLHint_Function_ptr function = TDDLHint_Function_ptr(new TDDLHint_Function(name));

    functions->push_back(function);

    if (hash == FnvHash::Constants::MASTER)
    {
      if (*lexer->token() == Token::XEOF)
      {
        break;
      }
      else if (*lexer->token() == Token::BAR)
      {
        lexer->nextToken();
        continue;
      }
    }

    if (hash == FnvHash::Constants::SLAVE)
    {
      if (*lexer->token() == Token::XEOF)
      {
        break;
      }
      else if (*lexer->token() == Token::AND)
      {
        lexer->nextToken();
        continue;
      }
    }

    if (*lexer->token() == Token::AND)
    {
      continue;
    }

    hintParser->accept(Token::LPAREN);

    if (*lexer->token() != Token::RPAREN)
    {
      for (;;)
      {
        Lexer_SavePoint_ptr mark = lexer->mark();

        SQLExpr_ptr value = NULL;

        string_ptr keyVal = make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()));
        long keyHash = lexer->hashLCase();

        lexer->nextToken();

        SQLIdentifierExpr_ptr key = SQLIdentifierExpr_ptr(new SQLIdentifierExpr(keyVal, keyHash));

        if (*lexer->token() == Token::EQ)
        {
          hintParser->accept(Token::EQ);

          if (*lexer->token() == Token::LITERAL_ALIAS)
          {
            string_ptr stringVal = make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()));
            stringVal = make_string_ptr(stringVal->substr(1, stringVal->length() - 1));
            value = SQLCharExpr_ptr(new SQLCharExpr(stringVal));
            value = hintParser->exprRest(value);

            lexer->nextToken();
          }
          else
          {
            value = hintParser->expr();
          }
        }

        // 为了处理 add_ms(t.pk, BOOL::TRUE) 的情况；
        if (value == NULL)
        {
          lexer->reset(mark);
          key = NULL;
          value = hintParser->expr();
        }

        TDDLHint_Argument_ptr argument = TDDLHint_Argument_ptr(new TDDLHint_Argument(key, value));
        function->getArguments()->push_back(argument);

        if (*lexer->token() == Token::COMMA)
        {
          lexer->nextToken();
          continue;
        }

        if (*lexer->token() == Token::RPAREN)
        {
          lexer->nextToken();
          break;
        }
      }
    }
    else
    {
      lexer->nextToken();
    }

    if (*lexer->token() == Token::AND)
    {
      continue;
    }

    if (hash == FnvHash::Constants::MASTER && *lexer->token() == Token::BAR)
    {
      lexer->nextToken();
    }

    if (*lexer->token() == Token::XEOF)
    {
      break;
    }
  }

  if (functions->size() > 0)
  {
    type = TDDLHint_Type::Function;
  }
}
